Modern Cryptography

Generally speaking there are two kinds of encryption: symmetric and asymmetric.

In symmetric encryption, the parties involved share the same key.

In asymmetric encryption, the parties use different keys, that are mathematically related to each other.

Symmetric Encryption

In the following, we look at symmetric encryption algorithms. In symmetric crypto, we use the same key for encryption and decryption. Therefore, the two parties need to establish a secret key between them. Symmetric encryption can be up to 1000 times faster than asymmetric encryption. Given the support of some crypto algorithm in the CPU and at hardware level, even faster.

Exercise

  • How should we share this secret key? securely, of course!

Advanced Encryption Algorithm (AES)

AES is based on Rijndael encryption algorithm, designed by Joan Daemen and Vincent Rijmen. It was one of the algorithms submitted to U.S. National Institute of Standards and Technology (NIST) to replace DES and 3DES. It was published in 1998 and accepted and standardized in 2001.

  • AES supports key sizes of 128/192/256 bits
  • Block size: 128 bit
  • It's iterative rather than Feistel cipher
  • Treats data in 4 groups of 4 bytes
  • Operates on an entire block in every round
  • Resistant against known attacks
  • Speed and code compactness on many CPUs
  • Rijndael block and key size vary between 128, 192, 256
  • However, in AES block size in 128
  • Number of rounds a function of key size

    • 128 bits 10 rounds
    • 192 bits 12 rounds
    • 256 bits 14 rounds
  • Today most implementations use the CPU support (Intel AES-NI)

Block cipher mode of operation

To encrypt messages of arbitrary size with block ciphers, we use the following algorithms, called the modes of operation. They define how to encrypt each block of the plaintext to produce the corresponding cipher text block. Some of these are completely insecure (ECB) and should not be used.

  • Electronic Codebook (ECB)
  • Cipher Block Chaining (CBC)
  • Counter (CTR)

Electronic Codebook (ECB)

Cipher Block Chaining (CBC)

Counter (CTR)


In [ ]:
import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
key = os.urandom(16) # in bytes, 128 bits
iv = os.urandom(16)

In [ ]:
# ECB Mode, we only need a key
### *** DO NOT USE ECB. IT IS INSECURE *** ###

cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend())
encryptor = cipher.encryptor()
# note that we don't need padding here, since len("PyCon 2017 Cypto") = 16
cipher_text = encryptor.update(b"PyCon 2017 Cypto") + encryptor.finalize()

In [ ]:
cipher_text

In [ ]:
print (len(cipher_text))

In [ ]:
decryptor = cipher.decryptor()
decryptor.update(cipher_text) + decryptor.finalize()

In [ ]:
# CBC Mode, we also need an IV
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
encryptor = cipher.encryptor()
# note that we don't need padding here, since len("PyCon 2017 Cypto") = 16
cipher_text = encryptor.update(b"PyCon 2017 Cypto") + encryptor.finalize()

In [ ]:
cipher_text

In [ ]:
decryptor = cipher.decryptor()
decryptor.update(cipher_text) + decryptor.finalize()

In [ ]:
# CTR Mode, we don't need padding in CTR mode. In transforms a block cipher into a stream cipher
# we only need to introduce the nonce
cipher = Cipher(algorithms.AES(key), modes.CTR(os.urandom(16)), backend=default_backend())
encryptor = cipher.encryptor()
# len(b"PyCon 2017 Cypto!!") = 18, however no padding is needed.
cipher_text = encryptor.update(b"PyCon 2017 Cypto!!") + encryptor.finalize()

Exercise

  • Encrypt the file following text using the ECB, and CBC or CTR mode and compare the results.

In [ ]:
plain_text = b"PyCon is great!!" * 128

In [ ]:
def print_text(text, b64=False):
    for i in range(0, 128, 16):
        if b64:
            pt = base64.b64encode(text[i:i+16])
        else:
            pt = text[i:i+16]
        print (pt)

In [ ]:
## IMPLEMENTATION

In [ ]:
print_text(ecb_ct)

In [ ]:
print_text(cbc_ct)

In [ ]:
print_text(ctr_ct)

Extra Activity

Encrypt the file "include/tux.png" using the ECB, and CBC or CTR mode and compare the results.

  • You need to install the pillow library
    • read the image file and get all the pixel values
    • encrypt the pixel values
    • create a new image with new (encrypted) pixel values
    • write your newly crafted image to disk

To install Pillow, simply use pip or conda:

pip install Pillow
 conda install pillow

The origianl image from Wikipedia.


ECB Encryption of the image with two different keys. The results (colors) are different, because we are using two different keys. However, the patterns inside the data (image) is not hidden.

<img src="include/ECB2.png", width="200">


As compared to when we are using the CBC (or CTR) mode. Because we introduce the randomness at the beginning (IV), and we carry this randomness (noise) throughout the encryption the patterns are diminished.

Encryption alone is not good enough

Encrypting your data alone will not protect you from data tampering, meaning an adversary can change the results of your decryption without having access to the key -- all without you noticing. That's where HMACs discussed previously become handy.

Bit flipping attack

Since the IV is sent in clear we can change the IV value and change the corresponding plaintext, when using CBC mode.

Encryption

  • $C_{i}=E_{K}(P_{i}\oplus C_{i-1})$
  • $ C_{0}=IV$

Decryption

  • $P_{i}=D_{K}(C_{i})\oplus C_{i-1}$
  • $C_{0}=IV$

Therefore to change the plaintext value we just need to xor the old plaintext(p), and the new value (t), with the IV:

$IV = IV \oplus p \oplus t$

Meaning if the first 4 bytes of the plaint text are: "1234" and we want to change it to "6789" all we have to do is

$IV[0:4] = IV[0:4] \oplus 1234 \oplus 6789$

Exercise

  • Imagine the message is only 16 bytes, "PyCon2017 Crypto". Change the year from 2017 to 1991.

In [ ]:
def xor(s1, s2):
    return bytes([a ^ b for a,b in zip(s1,s2)])

In [ ]:
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
encryptor = cipher.encryptor()
cipher_text = encryptor.update(b"PyCon2017 Crypto") + encryptor.finalize()

In [ ]:
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
decryptor = cipher.decryptor()
decryptor.update(cipher_text) + decryptor.finalize()

In [ ]:
## IMPLEMENTATION


Authenticated Encryption with Associated Data (AEAD)

AEAD provides confidentiality, integrity, and authenticity at once. Such schemes help to mitigate against the bit flipping attacks that we just did. The Galois/Counter Mode (GCM) mode of operation is the recommended schemes to be used. Fortunately, the cryptography library already has it implemented.


In [ ]:
# GCM Mode, we also need an IV
cipher = Cipher(algorithms.AES(key), modes.GCM(iv), backend=default_backend())
encryptor = cipher.encryptor()
# note that we don't need padding here, since len("PyCon2017 Crypto") = 16
encryptor.authenticate_additional_data(b"SOME ADDITIONAL DATA")
cipher_text = encryptor.update(b"PyCon2017 Crypto") + encryptor.finalize()
tag = encryptor.tag

In [ ]:
decryptor = Cipher(algorithms.AES(key), modes.GCM(iv,tag), backend=default_backend()).decryptor()
decryptor.authenticate_additional_data(b"SOME ADDITIONAL DATA")
decryptor.update(cipher_text) + decryptor.finalize()

Padding

With some block cipher mode of operations (e.g., CBC) we need to pad the data to the block size. Otherwise, if would throw an exception.


In [ ]:
import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
key = os.urandom(16) # in bytes, 128 bits

#CTR
cipher = Cipher(algorithms.AES(key), modes.CTR(os.urandom(16)), backend=default_backend())
encryptor = cipher.encryptor()
# len("PyCon 2017 Cryptography") = 23, but we don't need padding in CTR
ctr_ct = encryptor.update(b"PyCon 2017 Cryptography") + encryptor.finalize()

In [ ]:
#CBC
cipher = Cipher(algorithms.AES(key), modes.CBC(os.urandom(16)), backend=default_backend())
encryptor = cipher.encryptor()
# len("PyCon 2017 Cryptography") = 23, throws an exception
cbc_ct = encryptor.update(b"PyCon 2017 Cryptography") + encryptor.finalize()

Public-Key Cryptography Standards (PKCS)

PKCS7 padding is described in RFC 5652. The number of missing bytes (n) to the whole block size is repeated n times.

  • For example if the block size is 16,
    • The data is of size 13, the data is padding with 3, 3 times. 03 03 03.
    • The data is of size 14, the data is padded with 2, 2 times. 02 02

Exercise

  • Implement PKCS7 padding

In [ ]:
## IMPLEMENTATION
def pkcs7_pad(text):
    pass

In [ ]:
# Test our implementation of PKCS7 padding

from cryptography.hazmat.primitives import padding

for i in range(16):
    msg = b'A'*i
    padder = padding.PKCS7(128).padder()
    padded_data = padder.update(msg)
    padded_data += padder.finalize()
    assert padded_data == pkcs7_pad(msg)